package com.zy.explore.handler;

import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.netflix.hystrix.exception.HystrixBadRequestException;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.zy.explore.constant.MessageCode;
import com.zy.explore.constant.StatusCode;
import com.zy.explore.vo.ResultVO;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import lombok.var;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.server.ResponseStatusException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletionException;
import java.util.stream.Collectors;

/**
 * description 全局Controller异常捕获
 *
 * @author ZY
 * @date 2021-05-05 18:05
 */
@Slf4j
@RestController
@ConditionalOnProperty(value = ProjectExceptionHandler.ENABLED, matchIfMissing = true)
@RequestMapping("${server.error.path:${error.path:/error}}")
@AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
public class ProjectExceptionHandler implements ErrorController {

    static final String ENABLED = "spring.wenwo.error-controller.enabled";

    private final ErrorProperties errorProperties;

    private final ErrorAttributes errorAttributes;

    public ProjectExceptionHandler(ServerProperties serverProperties, ErrorAttributes errorAttributes) {
        this.errorProperties = serverProperties.getError();
        this.errorAttributes = errorAttributes;
    }

    private static Throwable unwrapCause(Throwable e) {
        if (e instanceof StatusCode || e instanceof ResponseStatusException) {
            return e;
        }

        Throwable result = e;

        while (result instanceof HystrixRuntimeException || result instanceof HystrixBadRequestException || result instanceof CompletionException) {
            Throwable cause = result.getCause();
            if (cause == null) {
                break;
            }

            result = cause;

            if (result instanceof StatusCode || result instanceof ResponseStatusException) {
                break;
            }
        }

        return result;
    }

    private static String buildInvalidFormatMessage(InvalidFormatException cause) {
        val targetType = cause.getTargetType();
        val value = cause.getValue();
        val property = cause.getPath().isEmpty() ? "" :
            cause.getPath().iterator().next().getFieldName();

        var message = "[" + property + "]: 输入 [" + value + "] 解析错误";

        if (targetType.isEnum()) {
            message += "，允许的值为 " + Arrays.toString(targetType.getEnumConstants()) + "";
        }

        message += "，无法反序列化为 [" + targetType.getName() + "]";

        return message;
    }

    private static boolean getTraceParameter(HttpServletRequest request) {
        String parameter = request.getParameter("trace");
        if (parameter == null) {
            return false;
        }
        return !"false".equalsIgnoreCase(parameter);
    }

    private static ResponseEntity<ResultVO<?>> respond(int status, String message) {
        return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
            .body(ResultVO.getError(status, message));
    }

    @Override
    public String getErrorPath() {
        return errorProperties.getPath();
    }

    /**
     * 异常处理函数，处理所有异常
     *
     * @param request Servlet Request
     * @return 响应体
     */
    @RequestMapping
    public ResponseEntity<ResultVO<?>> handle(HttpServletRequest request) {
        val webRequest = new ServletWebRequest(request);
        var e = errorAttributes.getError(webRequest);

        if (e == null) {
            val errorMap = errorAttributes.getErrorAttributes(webRequest, isIncludeStackTrace(request));
            val status = errorMap.get("status");
            val message = errorMap.get("message");
            int code;
            String msg;

            if (status instanceof Number) {
                code = ((Number) status).intValue();
                if (code < 200) {
                    code = 500;
                }
            } else {
                code = 500;
            }

            if (code == 404) {
                return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
                    .body(ResultVO.getError(MessageCode.NOT_FOUND));
            }

            if (message instanceof CharSequence) {
                msg = message.toString();
            } else {
                msg = "服务器出错";
            }

            log.error("url:{} unknown error: {}", request.getRequestURL(), errorMap);

            return respond(code, msg);
        }

        return handleError(request, e);
    }

    public ResponseEntity<ResultVO<?>> handleError(HttpServletRequest request, Throwable e) {
        val err = unwrapCause(e);

        // 业务错误处理
        if (err instanceof StatusCode) {
            StatusCode ex = (StatusCode) err;

            log.error("BaseException: code: {}, message: {}", ex.getCode(), ex.getMessage());

            return respond(ex.getCode(), ex.getMessage());
        }

        // 标准Response Status异常
        if (err instanceof ResponseStatusException) {
            val ex = (ResponseStatusException) err;
            var message = ex.getMessage();
            if (message == null) {
                message = ex.getReason();
            }
            if (message == null) {
                message = ex.getStatus().getReasonPhrase();
            }

            log.error("ResponseStatusException: status: {}, message: {}", ex.getStatus(), message);

            return respond(ex.getStatus().value(), message);
        }

        // 参数验证
        if (err instanceof MethodArgumentNotValidException || err instanceof BindException) {
            List<ObjectError> errors;

            if (err instanceof MethodArgumentNotValidException) {
                errors = ((MethodArgumentNotValidException) err).getBindingResult().getAllErrors();
            } else {
                errors = ((BindException) err).getBindingResult().getAllErrors();
            }

            val message = errors.stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(";"));

            log.error("url:{} params valid error: {}", request.getRequestURL(), message);

            return respond(MessageCode.PARAMETER_NULL_ERROR.getCode(), message);
        }

        // 参数验证
        if (err instanceof ConstraintViolationException) {
            val ex = (ConstraintViolationException) err;
            val violations = ex.getConstraintViolations();
            val message = violations.stream().map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(";"));
            log.error("url:{} params valid error: {}", request.getRequestURL(), message);
            return respond(MessageCode.PARAMETER_NULL_ERROR.getCode(), message);
        }

        // 参数解析异常
        if (err instanceof InvalidFormatException || err instanceof HttpMessageNotReadableException) {
            Throwable error;

            if (err.getCause() == null) {
                error = err;
            } else {
                error = err.getCause();
            }

            var message = error.getMessage();

            if (error instanceof InvalidFormatException) {
                message = buildInvalidFormatMessage((InvalidFormatException) error);
            }

            log.error("url:{} params format error: {}", request.getRequestURL(), message);
            return respond(MessageCode.PARAMETER_NULL_ERROR.getCode(), message);
        }

        val responseStatus = MergedAnnotations.from(err.getClass(), MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
        val status = responseStatus.getValue("code", HttpStatus.class).orElse(HttpStatus.INTERNAL_SERVER_ERROR);
        val reason = responseStatus.getValue("reason", String.class).orElse("");
        String message;

        if (reason.isEmpty()) {
            message = err.getMessage() != null ? err.getMessage() : "服务器出错";
        } else {
            message = reason;
        }

        // 程序异常
        log.error("ExceptionHandler:", err);
        return respond(status.value(), message);
    }

    private boolean isIncludeStackTrace(HttpServletRequest request) {
        ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }
}
